package org.dreamwork.cai.spring;

import org.dreamwork.cai.ServiceNotFoundException;
import org.dreamwork.cai.core.impl.AbstractRemoteServices;
import org.dreamwork.cai.proxy.RemoteProxyHandler;
import org.dreamwork.cai.srf.ServiceRouteFrame;
import org.dreamwork.cai.srf.ServiceRouteItem;
import org.dreamwork.cai.tools.Const;
import org.dreamwork.cai.tools.Misc;
import org.dreamwork.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.AutowireCandidateResolver;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.stereotype.Component;

import javax.annotation.PreDestroy;
import java.util.*;

/**
 * 远程服务在本地 spring context 中的代理器.
 *
 * <p>所有针对远程服务的调用，都从这个代理器中获取实际的{@code 远程代理对象}</p>
 */
@Component
public class SpringRemoteServicesBridge extends AbstractRemoteServices/* implements IRemoteServices */{
    private ApplicationContext context;
    private ApplicationContext parent;

    /**
     * 虚拟的父 context。所有正常的spring的获取远程实例的操作都会被转到这个context来。
     */
    private GenericApplicationContext root;

    /**
     * 每个 srf 对应的 ApplicationContext 对象都被缓存在这个缓存池内
     */
    private final Map<String, GenericApplicationContext> pool = Collections.synchronizedMap (new HashMap<> ());

    public void setContext (ApplicationContext context) {
        this.context = context;
        if (autowireSupported) {
            this.parent = context.getParent ();

            GenericApplicationContext gac = (GenericApplicationContext) context;
            DefaultListableBeanFactory dbf = gac.getDefaultListableBeanFactory ();
            if (parent != null) {
                root = new GenericApplicationContext (new ShadowFactory (), parent);
            } else {
                root = new GenericApplicationContext (new ShadowFactory ());
            }
            root.refresh ();

            gac.setParent (root);
            AutowireCandidateResolver resolver = dbf.getAutowireCandidateResolver ();
            ((ContextAnnotationAutowireCandidateResolver) resolver).setBeanFactory (new ShadowFactory (dbf));
        }
    }

    /**
     * 根据给定的 {@code namespace} 和 {@code name} 来查找远程服务的 {@code 代理接口}.
     * @param namespace 远程服务的命名空间
     * @param name      服务名称
     * @return 远程服务的 {@code 代理接口}
     * @throws ServiceNotFoundException 若远程服务无效，则抛出该错误
     */
    public Object lookup (String namespace, String name) throws ServiceNotFoundException {
        ApplicationContext ctx = pool.get (namespace);
        if (ctx != null) {
            try {
                return ctx.getBean (name);
            } catch (Exception ex) {
                throw new ServiceNotFoundException ("service [" + namespace + "." + name + "] not found.", ex);
            }
        }
        throw new ServiceNotFoundException ("service [" + namespace + "." + name + "] not found.");
    }

    /**
     * 根据给定的 {@code namespace} 和 类型来查找远程服务的 {@code 代理接口}.
     * @param namespace 远程服务的命名空间
     * @param type      远程服务类型。这个类型必须在本地能够被加载
     * @param <T>       远程服务类型
     * @return 远程服务的 {@code 代理接口}
     * @throws ServiceNotFoundException 若远程服务无效，则抛出该错误
     */
    public<T> T lookup (String namespace, Class<T> type) throws ServiceNotFoundException {
        ApplicationContext ctx = pool.get (namespace);
        if (ctx != null) {
            try {
                return ctx.getBean (type);
            } catch (Exception ex) {
                throw new ServiceNotFoundException ("service [" + namespace + ",type=" + type + "]", ex);
            }
        }
        throw new ServiceNotFoundException ("service [" + namespace + ",type=" + type + "] not found.");
    }

    /**
     * 注册一个 SRF 所对应的 Spring ApplicationContext.
     * @param srf 远程服务路由帧
     * @throws Exception 任何错误
     */
    @Override
    public void register (ServiceRouteFrame srf) throws Exception {
        GenericApplicationContext ctx = new GenericApplicationContext (context);
        ctx.setDisplayName (srf.namespace);

        // 从网络接收的 srf 中的 sri 中的 MethodInfo 中有缺失的信息，需要补齐
        DefaultListableBeanFactory factory = ctx.getDefaultListableBeanFactory ();
        Set<ServiceRouteItem> copy = new HashSet<> (srf.items.values ());
        srf.items.clear ();
        for (ServiceRouteItem src : copy) {
            ServiceRouteItem sri = new ServiceRouteItem (src.name, srf);

            Class<?>[] types = new Class<?> [src.types.length];
            for (int i = 0; i < types.length; i ++) {
                types[i] = Misc.translate (src.types[i]);
            }
            sri.setTypes (Arrays.asList (types));
            Misc.patchMethodInfo (src, sri, types[0]);

            // 创建远程代理对象
//            Object proxy = DynamicProxyFactory.getProxy (sri, null, types);
            Object proxy = RemoteProxyHandler.create (sri, types);
            // 准备将远程代理注入 spring context
            GenericBeanDefinition def = new GenericBeanDefinition ();
            MutablePropertyValues props = def.getPropertyValues ();
            props.addPropertyValue ("proxyInterfaces", types);
            props.addPropertyValue ("target", proxy);
            def.setBeanClass (ProxyFactoryBean.class);
            factory.registerBeanDefinition (src.name, def);

            srf.add (sri);
        }
        ctx.refresh ();
        pool.put (srf.namespace, ctx);
        sendBroadcast (Const.SRF.BUILT, srf);
    }

    /**
     * 从缓存中删除 SRF 对应的 Spring ApplicationContext
     * @param namespace SRF 的命名空间
     */
    public synchronized void remove (String namespace) {
        if (pool.containsKey (namespace)) {
            GenericApplicationContext context = pool.get (namespace);
            context.close ();
            pool.remove (namespace);
            sendBroadcast (Const.SRF.DESTROY, namespace);
        }
    }

    @PreDestroy
    public void destroy () {
        if (root != null) {
            ((GenericApplicationContext) context).setParent (this.parent);
            root.close ();
        }
        root = null;
    }

    // ========================== inner class ====================================
    private class ShadowFactory extends DefaultListableBeanFactory {
        private final Logger logger = LoggerFactory.getLogger (getClass ());

        ShadowFactory () {}

        ShadowFactory (DefaultListableBeanFactory parent) {
            super (parent);
        }

        @Override
        public Object doResolveDependency (DependencyDescriptor descriptor, String beanName, Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException {
            try {
                return super.doResolveDependency (descriptor, beanName, autowiredBeanNames, typeConverter);
            } catch (BeansException ex) {
                logger.warn (ex.getMessage (), ex);
            }

            for (GenericApplicationContext ctx : pool.values ()) {
                try {
                    return ctx.getDefaultListableBeanFactory ().doResolveDependency (descriptor, beanName, autowiredBeanNames, typeConverter);
                } catch (BeansException ex) {
                    logger.warn (ex.getMessage (), ex);
                }
            }

            throw new NoSuchBeanDefinitionException (beanName);
        }

        @Override
        public Object getBean (String name) throws BeansException {
            for (GenericApplicationContext ctx : pool.values ()) {
                try {
                    return ctx.getBean (name);
                } catch (BeansException ex) {
                    logger.warn (ex.getMessage (), ex);
                }
            }

            return super.getBean (name);
        }

        @Override
        public <T> T getBean (String name, Class<T> requiredType) throws BeansException {
            for (GenericApplicationContext ctx : pool.values ()) {
                try {
                    return ctx.getBean (name, requiredType);
                } catch (BeansException ex) {
                    logger.warn (ex.getMessage (), ex);
                }
            }

            return super.getBean (name, requiredType);
        }

        @Override
        public <T> T getBean (Class<T> requiredType) throws BeansException {
            for (GenericApplicationContext ctx : pool.values ()) {
                try {
                    return ctx.getBean (requiredType);
                } catch (BeansException ex) {
                    logger.warn (ex.getMessage (), ex);
                }
            }

            return super.getBean (requiredType);
        }

        @Override
        public BeanDefinition getBeanDefinition (String beanName) throws NoSuchBeanDefinitionException {
            if (!StringUtil.isEmpty (beanName) && beanName.contains ("/")) {
                String[] a = beanName.split ("/");
                String namespace = a[0];
                beanName = a[1];

                if (pool.containsKey (namespace)) {
                    DefaultListableBeanFactory factory = pool.get (namespace).getDefaultListableBeanFactory ();
                    try {
                        Class<?> type = Class.forName (beanName);
                        String[] names = factory.getBeanNamesForType (type);
                        if (names.length > 0) {
                            return pool.get (namespace).getDefaultListableBeanFactory ().getBeanDefinition (names[0]);
                        }
                    } catch (Exception ex) {
                        throw new NoSuchBeanDefinitionException (beanName);
                    }
                }
            }
            return super.getBeanDefinition (beanName);
        }
    }
}